package org.liujing.jsw.client.ui;

import com.google.gwt.core.client.*;

import java.io.Serializable;
public class SizeRequirements implements Serializable {

    /**
     * The minimum size required.
     * For a component <code>comp</code>, this should be equal to either
     * <code>comp.getMinimumSize().width</code> or
     * <code>comp.getMinimumSize().height</code>.
     */
    public int minimum;

    /**
     * The preferred (natural) size.
     * For a component <code>comp</code>, this should be equal to either
     * <code>comp.getPreferredSize().width</code> or
     * <code>comp.getPreferredSize().height</code>.
     */
    public int preferred;

    /**
     * The maximum size allowed.
     * For a component <code>comp</code>, this should be equal to either
     * <code>comp.getMaximumSize().width</code> or
     * <code>comp.getMaximumSize().height</code>.
     */
    public int maximum;

    /**
     * The alignment, specified as a value between 0.0 and 1.0,
     * inclusive.
     * To specify centering, the alignment should be 0.5.
     */
    public float alignment;

    /**
     * Creates a SizeRequirements object with the minimum, preferred,
     * and maximum sizes set to zero and an alignment value of 0.5
     * (centered).
     */
    public SizeRequirements() {
	minimum = 0;
	preferred = 0;
	maximum = 0;
	alignment = 0.5f;
    }

    /**
     * Creates a SizeRequirements object with the specified minimum, preferred,
     * and maximum sizes and the specified alignment.
     *
     * @param min the minimum size >= 0
     * @param pref the preferred size >= 0
     * @param max the maximum size >= 0
     * @param a the alignment >= 0.0f && <= 1.0f
     */
    public SizeRequirements(int min, int pref, int max, float a) {
	minimum = min;
	preferred = pref;
	maximum = max;
	alignment = a > 1.0f ? 1.0f : a < 0.0f ? 0.0f : a;
    }

    /**
     * Returns a string describing the minimum, preferred, and maximum
     * size requirements, along with the alignment.
     *
     * @return the string
     */
    public String toString() {
	return "[" + minimum + "," + preferred + "," + maximum + "]@" + alignment;
    }

    /**
     * Determines the total space necessary to
     * place a set of components end-to-end.  The needs
     * of each component in the set are represented by an entry in the
     * passed-in SizeRequirements array.
     * The returned SizeRequirements object has an alignment of 0.5
     * (centered).  The space requirement is never more than
     * Integer.MAX_VALUE.
     *
     * @param children  the space requirements for a set of components.
     *   The vector may be of zero length, which will result in a
     *   default SizeRequirements object instance being passed back.
     * @return  the total space requirements.
     */
    public static SizeRequirements getTiledSizeRequirements(SizeRequirements[]
							    children) {
	SizeRequirements total = new SizeRequirements();
	for (int i = 0; i < children.length; i++) {
	    SizeRequirements req = children[i];
	    total.minimum = (int) Math.min((long) total.minimum + (long) req.minimum, Integer.MAX_VALUE);
	    total.preferred = (int) Math.min((long) total.preferred + (long) req.preferred, Integer.MAX_VALUE);
	    total.maximum = (int) Math.min((long) total.maximum + (long) req.maximum, Integer.MAX_VALUE);
	}
	return total;
    }

    /**
     * Determines the total space necessary to
     * align a set of components.  The needs
     * of each component in the set are represented by an entry in the
     * passed-in SizeRequirements array.  The total space required will
     * never be more than Integer.MAX_VALUE.
     *
     * @param children  the set of child requirements.  If of zero length,
     *  the returns result will be a default instance of SizeRequirements.
     * @return  the total space requirements.
     */
    public static SizeRequirements getAlignedSizeRequirements(SizeRequirements[]
							      children) {
	SizeRequirements totalAscent = new SizeRequirements();
	SizeRequirements totalDescent = new SizeRequirements();
	for (int i = 0; i < children.length; i++) {
	    SizeRequirements req = children[i];

	    int ascent = (int) (req.alignment * req.minimum);
	    int descent = req.minimum - ascent;
	    totalAscent.minimum = Math.max(ascent, totalAscent.minimum);
	    totalDescent.minimum = Math.max(descent, totalDescent.minimum);

	    ascent = (int) (req.alignment * req.preferred);
	    descent = req.preferred - ascent;
	    totalAscent.preferred = Math.max(ascent, totalAscent.preferred);
	    totalDescent.preferred = Math.max(descent, totalDescent.preferred);

	    ascent = (int) (req.alignment * req.maximum);
	    descent = req.maximum - ascent;
	    totalAscent.maximum = Math.max(ascent, totalAscent.maximum);
	    totalDescent.maximum = Math.max(descent, totalDescent.maximum);
	}
	int min = (int) Math.min((long) totalAscent.minimum + (long) totalDescent.minimum, Integer.MAX_VALUE);
	int pref = (int) Math.min((long) totalAscent.preferred + (long) totalDescent.preferred, Integer.MAX_VALUE);
	int max = (int) Math.min((long) totalAscent.maximum + (long) totalDescent.maximum, Integer.MAX_VALUE);
	float alignment = 0.0f;
	if (min > 0) {
		alignment = (float) totalAscent.minimum / min;
	    //GWT.log("getAlignedSizeRequirements() totalAscent.minimum=" + totalAscent.minimum + " min=" + min + " alignment=" + alignment);
	    alignment = alignment > 1.0f ? 1.0f : alignment < 0.0f ? 0.0f : alignment;
	}
	return new SizeRequirements(min, pref, max, alignment);
    }

    /**
     * Creates a set of offset/span pairs representing how to
     * lay out a set of components end-to-end.
     * This method requires that you specify
     * the total amount of space to be allocated,
     * the size requirements for each component to be placed
     * (specified as an array of SizeRequirements), and
     * the total size requirement of the set of components.
     * You can get the total size requirement
     * by invoking the getTiledSizeRequirements method.  The components
     * will be tiled in the forward direction with offsets increasing from 0.
     *
     * @param allocated the total span to be allocated >= 0.
     * @param total     the total of the children requests.  This argument
     *  is optional and may be null.
     * @param children  the size requirements for each component.
     * @param offsets   the offset from 0 for each child where
     *   the spans were allocated (determines placement of the span).
     * @param spans     the span allocated for each child to make the
     *   total target span.
     */
    public static void calculateTiledPositions(int allocated,
					       SizeRequirements total,
				               SizeRequirements[] children,
				               int[] offsets,
					       int[] spans) {
        calculateTiledPositions(allocated, total, children, offsets, spans, true);
    }

    /**
     * Creates a set of offset/span pairs representing how to
     * lay out a set of components end-to-end.
     * This method requires that you specify
     * the total amount of space to be allocated,
     * the size requirements for each component to be placed
     * (specified as an array of SizeRequirements), and
     * the total size requirement of the set of components.
     * You can get the total size requirement
     * by invoking the getTiledSizeRequirements method.
     *
     * This method also requires a flag indicating whether components
     * should be tiled in the forward direction (offsets increasing
     * from 0) or reverse direction (offsets decreasing from the end
     * of the allocated space).  The forward direction represents
     * components tiled from left to right or top to bottom.  The
     * reverse direction represents components tiled from right to left
     * or bottom to top.
     *
     * @param allocated the total span to be allocated >= 0.
     * @param total     the total of the children requests.  This argument
     *  is optional and may be null.
     * @param children  the size requirements for each component.
     * @param offsets   the offset from 0 for each child where
     *   the spans were allocated (determines placement of the span).
     * @param spans     the span allocated for each child to make the
     *   total target span.
     * @param forward   tile with offsets increasing from 0 if true 
     *   and with offsets decreasing from the end of the allocated space
     *   if false.
     * @since 1.4
     */
    public static void calculateTiledPositions(int allocated,
					       SizeRequirements total,
				               SizeRequirements[] children,
				               int[] offsets,
					       int[] spans,
                                               boolean forward) {
	// The total argument turns out to be a bad idea since the
	// total of all the children can overflow the integer used to
	// hold the total.  The total must therefore be calculated and
	// stored in long variables.
	long min = 0;
	long pref = 0;
	long max = 0;
	for (int i = 0; i < children.length; i++) {
	    min += children[i].minimum;
	    pref += children[i].preferred;
	    max += children[i].maximum;
	    //GWT.log("calculateTiledPositions() min="+ min+", pref="+ pref + ", max="+ max);
	}
	if (allocated >= pref) {
	    expandedTile(allocated, min, pref, max, children, offsets, spans, forward);
	} else {
	    compressedTile(allocated, min, pref, max, children, offsets, spans, forward);
	}
    }

    private static void compressedTile(int allocated, long min, long pref, long max,
				       SizeRequirements[] request,
				       int[] offsets, int[] spans,
                                       boolean forward) {

	// ---- determine what we have to work with ----
	float totalPlay = Math.min(pref - allocated, pref - min);
	float factor = (pref - min == 0) ? 0.0f : totalPlay / (pref - min);

	// ---- make the adjustments ----
	int totalOffset;
        if( forward ) {
            // lay out with offsets increasing from 0
            totalOffset = 0;
            for (int i = 0; i < spans.length; i++) {
                offsets[i] = totalOffset;
                SizeRequirements req = request[i];
                float play = factor * (req.preferred - req.minimum);
                spans[i] = (int)(req.preferred - play);
                totalOffset = (int) Math.min((long) totalOffset + (long) spans[i], Integer.MAX_VALUE);
            }
	} else {
            // lay out with offsets decreasing from the end of the allocation
            totalOffset = allocated;
            for (int i = 0; i < spans.length; i++) {
                SizeRequirements req = request[i];
                float play = factor * (req.preferred - req.minimum);
                spans[i] = (int)(req.preferred - play);
                offsets[i] = totalOffset - spans[i];
                totalOffset = (int) Math.max((long) totalOffset - (long) spans[i], 0);
            }
        }
    }

    private static void expandedTile(int allocated, long min, long pref, long max,
				     SizeRequirements[] request,
				     int[] offsets, int[] spans,
                                     boolean forward) {

	// ---- determine what we have to work with ----
	float totalPlay = Math.min(allocated - pref, max - pref);
	float factor = (max - pref == 0) ? 0.0f : totalPlay / (max - pref);

	// ---- make the adjustments ----
	int totalOffset;
        if( forward ) {
            // lay out with offsets increasing from 0
            totalOffset = 0;
            for (int i = 0; i < spans.length; i++) {
                offsets[i] = totalOffset;
                SizeRequirements req = request[i];
                int play = (int)(factor * (req.maximum - req.preferred));
                spans[i] = (int) Math.min((long) req.preferred + (long) play, Integer.MAX_VALUE);
                totalOffset = (int) Math.min((long) totalOffset + (long) spans[i], Integer.MAX_VALUE);
            }
        } else {
            // lay out with offsets decreasing from the end of the allocation
            totalOffset = allocated;
            for (int i = 0; i < spans.length; i++) {
                SizeRequirements req = request[i];
                int play = (int)(factor * (req.maximum - req.preferred));
                spans[i] = (int) Math.min((long) req.preferred + (long) play, Integer.MAX_VALUE);
                offsets[i] = totalOffset - spans[i];
                totalOffset = (int) Math.max((long) totalOffset - (long) spans[i], 0);
            }
        }
    }

    /**
     * Creates a bunch of offset/span pairs specifying how to
     * lay out a set of components with the specified alignments.
     * The resulting span allocations will overlap, with each one
     * fitting as well as possible into the given total allocation.
     * This method requires that you specify
     * the total amount of space to be allocated,
     * the size requirements for each component to be placed
     * (specified as an array of SizeRequirements), and
     * the total size requirements of the set of components
     * (only the alignment field of which is actually used).
     * You can get the total size requirement by invoking
     * getAlignedSizeRequirements.
     *
     * Normal alignment will be done with an alignment value of 0.0f
     * representing the left/top edge of a component.
     *
     * @param allocated the total span to be allocated >= 0.
     * @param total     the total of the children requests.
     * @param children  the size requirements for each component.
     * @param offsets   the offset from 0 for each child where
     *   the spans were allocated (determines placement of the span).
     * @param spans     the span allocated for each child to make the
     *   total target span.
     */
    public static void calculateAlignedPositions(int allocated,
                                                 SizeRequirements total,
				                 SizeRequirements[] children,
				                 int[] offsets,
						 int[] spans) {
        calculateAlignedPositions( allocated, total, children, offsets, spans, true );
    }

    /**
     * Creates a set of offset/span pairs specifying how to
     * lay out a set of components with the specified alignments.
     * The resulting span allocations will overlap, with each one
     * fitting as well as possible into the given total allocation.
     * This method requires that you specify
     * the total amount of space to be allocated,
     * the size requirements for each component to be placed
     * (specified as an array of SizeRequirements), and
     * the total size requirements of the set of components
     * (only the alignment field of which is actually used) 
     * You can get the total size requirement by invoking
     * getAlignedSizeRequirements.
     *
     * This method also requires a flag indicating whether normal or
     * reverse alignment should be performed.  With normal alignment
     * the value 0.0f represents the left/top edge of the component
     * to be aligned.  With reverse alignment, 0.0f represents the 
     * right/bottom edge.
     *
     * @param allocated the total span to be allocated >= 0.
     * @param total     the total of the children requests.
     * @param children  the size requirements for each component.
     * @param offsets   the offset from 0 for each child where
     *   the spans were allocated (determines placement of the span).
     * @param spans     the span allocated for each child to make the
     *   total target span.
     * @param normal    when true, the alignment value 0.0f means 
     *   left/top; when false, it means right/bottom.
     * @since 1.4
     */
    public static void calculateAlignedPositions(int allocated,
                                                 SizeRequirements total,
				                 SizeRequirements[] children,
				                 int[] offsets,
						 int[] spans,
                                                 boolean normal) {
        float totalAlignment = normal ? total.alignment : 1.0f - total.alignment;
        int totalAscent = (int)(allocated * totalAlignment);
        int totalDescent = allocated - totalAscent;
        //GWT.log("calculateAlignedPositions() "
        //    	+ " ,total.alignment=" + total.alignment
        //    	+ " ,allocated=" + totalDescent
        //    	+ " ,totalAlignment=" + totalAlignment
        //    	+ " ,totalAscent=" + totalAscent
        //    	+ " ,totalDescent=" + totalDescent
        //    	);
        for (int i = 0; i < children.length; i++) {
            SizeRequirements req = children[i];
            
            float alignment = normal ? req.alignment : 1.0f - req.alignment;
            int maxAscent = (int)(req.maximum * alignment);
            int maxDescent = req.maximum - maxAscent;
            int ascent = Math.min(totalAscent, maxAscent);
            int descent = Math.min(totalDescent, maxDescent);
            
            offsets[i] = totalAscent - ascent;
            spans[i] = (int) Math.min((long) ascent + (long) descent, Integer.MAX_VALUE);
            //GWT.log("calculateAlignedPositions() req=" + req + ", spans["+ i +"]="+spans[i] + " ascent="+ ascent + " descent=" + descent);
        }     
    }

    // This method was used by the JTable - which now uses a different technique. 
    /**
     * Adjust a specified array of sizes by a given amount.
     *
     * @param delta     an int specifying the size difference
     * @param children  an array of SizeRequirements objects
     * @return an array of ints containing the final size for each item
     */
    public static int[] adjustSizes(int delta, SizeRequirements[] children) {
      return new int[0];
    } 
}
